package jp.sourceforge.akjrcp.snack;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequence;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Track;

import jp.sourceforge.akjrcp.audio.Chorus;
import jp.sourceforge.akjrcp.subtitle.StandardSubObject;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/*
 * 
 * pitchToMIDI has a problem.
 * input ESPS data is wide,so cant contain pitch bend data.
 */
public class PitchToMIDI {
	public static Log log=LogFactory.getLog(PitchToMIDI.class);
	private int instrument=1;
	protected int shiftNote=0;
	public static String names[]={"C","C#","D","D#","E","F","F#","G","G#","A","A#","B"};
	
	protected int defaultVelocity=64;
	private boolean pitchNoVoice=true;  //what is this?
	private int volumeMin=500;	//900 //
    private boolean ignoreOffset=false;
    protected boolean forceInsert=true;
    protected int[] scaleFilter=null;//Scale.getMajorScale(Scale.SCALE_C);
	private String chorusLine=null;//"1+3+5";//"1+3+5+8+11+13-6-4";//"-8-6-4+1+3+5" "-8-6-4+16"

	private boolean parseVelocity=false;
	private boolean parseNote=false;
	
    public static double note_pitchs[]={
		/*
		13.75,
		14.567617838918553,
		15.433853467830245,
		16.351598147370062,
		17.323914765057356,
		18.35404833717995,
		19.445436838734228,
		20.601722677346945,
		21.82676484947196,
		23.124651819432007,
		24.499715164288165,
		25.95654403007546,
		*/
		27.5,
		29.135235559269688,
		30.867706810042687,
		32.7031961616527,
		34.6478293891135,
		36.708096524974316,
		38.890873519199936,
		41.20344518701423,
		43.65352952129351,
		46.24930345064996,
		48.999430129170484,
		51.913087848887784,
		55.0,
		58.27047088140453,
		61.73541336884975,
		65.40639205713052,
		69.29565849622452,
		73.41619275117742,
		77.7817467218628,
		82.40689003866913,
		87.30705868728619,
		92.4986065248718,
		97.99885985952928,
		103.8261752752493,
		110.0,
		116.54094128853936,
		123.47082623522826,
		130.8127835819113,
		138.59131642844417,
		146.8323849048125,
		155.56349281065155,
		164.81377940661966,
		174.61411666397075,
		184.99721229688743,
		195.99771892143522,
		207.6523497054461,
		220.0,
		233.08188162853938,
		246.9416514655141,
		261.6255660991232,
		277.1826317288786,
		293.6647686145403,
		311.126984355155,
		329.6275574718021,
		349.2282319067383,
		369.9944230880625,
		391.99543624762373,
		415.3046977207872,
		440.0,
		466.16376136,
		493.8833009211433,
		523.2511300688476,
		554.3652612017378,
		587.3295348389113,
		622.2539661780138,
		659.2551122607299,
		698.4564609710702,
		739.9888431647004,
		783.9908693047543,
		830.6093920613646,
		880.0,
		932.3275189258428,
		987.766597822517,
		1046.5022558788976,
		1108.7305178914366,
		1174.6590648974839,
		1244.5079272914352,
		1318.510219155711,
		1396.9129162573277,
		1479.9776803065513,
		1567.9817322285219,
		1661.2187773623093,
		1760.0,
		1864.6550302633707,
		1975.5331876054945,
		2093.0045032402,
		2217.4610267587955,
		2349.3181202342903,
		2489.0158444536855,
		2637.0204275799247,
		2793.82582114503,
		2959.9553485674046,
		3135.963451695071,
		3322.4375412037793,
		3520.0,
		3729.3100453501124,
		3951.0663591319108,
		4186.008989445209
		/*
		4434.922035469435,
		4698.636221347226,
		4978.0316686490005,
		5274.040833696853,
		5587.6516195508075,
		5919.91067304341,
		6271.9268778661935,
		6644.875055365877,
		7040.0,
		7458.620060346964,
		7902.132686105661,
		8372.017944820036,
		8869.844034842557,
		9397.27240445174,
		9956.06329678126,
		10548.081624467713,
		11175.303193623111,
		11839.821297904024,
		12543.853704684494,
		13289.750056648398,
		14080.0,
		14917.240059987414,
		15804.265307895013,
		16744.035821499314,
		17739.687997492496,
		18794.544732418064,
		19912.126512529045,
		21096.163163083453
		*/
	};
	//27.5 is Note 21  A0
	//4186 is Note 108 C8
	
	public static PitchNoteData pitchToNoteNumber(double pitch){
		return  pitchToNoteNumber(pitch,null);
	}
	public static final double under_0_note=note_pitchs[0]+(note_pitchs[1]-note_pitchs[0])/2;
	public static final double over_last_note=note_pitchs[note_pitchs.length-1]-(note_pitchs[note_pitchs.length-1]-note_pitchs[note_pitchs.length-1-1])/2;
	public static final int MIDI_OFFSET=21;
	
	public static class PitchNoteData{
		private int note;
		private double pitch;
		/**
		 * range -1 to 1
		 */
		private double level;
		public PitchNoteData(int note,double pitch,double level){
			this.note=note;
			this.pitch=pitch;
			this.level=level;
		}
		public int getNote() {
			return note;
		}
		public void setNote(int note) {
			this.note = note;
		}
		public double getPitch() {
			return pitch;
		}
		public void setPitch(double pitch) {
			this.pitch = pitch;
		}
		public double getLevel() {
			return level;
		}
		public void setLevel(double level) {
			this.level = level;
		}
		public String toString(){
			return "PitchNoteData:note="+note+",pitch="+pitch+",level="+level;
		}
		
		public boolean isNoVoice(){
			return pitch==0 && level==0;
		}
	}
	public static PitchNoteData pitchToNoteNumber(double pitch,int[] scaleFilters){
		
		//under value
		if(pitch<under_0_note){
			int note= filterdNote(0+MIDI_OFFSET,true,scaleFilters);
			return new PitchNoteData(note,pitch,0);
		}
		
		if(pitch>over_last_note){
			int note= filterdNote(note_pitchs.length-1+MIDI_OFFSET,true,scaleFilters);
			return new PitchNoteData(note,pitch,0);
		}
		
		for(int i=0;i<note_pitchs.length-1;i++){	//last one is already checked
			double base=noteNumberToPitch(i+MIDI_OFFSET);
			double base_plus=noteNumberToPitch(i+1+MIDI_OFFSET);
			if(pitch<base){
				//not so happen
				int note=  filterdNote(i+MIDI_OFFSET,false,scaleFilters);		//smaller than border
				
				return new PitchNoteData(note,pitch,0);
			}else if(pitch<base_plus){
				//half
				double half=(base_plus-base)/2;
				if(pitch>base+half){//over half
					int note= filterdNote(i+1+MIDI_OFFSET,false,scaleFilters);
					double level=-1*(base_plus-pitch)/(half*2);
					return new PitchNoteData(note,pitch,level);
				}else{
					int note= filterdNote(i+MIDI_OFFSET,true,scaleFilters);
					double level=0;
					if(pitch!=base){
						level=(base_plus-pitch)/(half*2);
					}
					return new PitchNoteData(note,pitch,level);
				}
			}
		}
		
		int note=  filterdNote(note_pitchs.length-1+MIDI_OFFSET,false,scaleFilters);//biggest last
		return new PitchNoteData(note,pitch,0);
	}
	
	public static int noteToScale(int note){
		return (note-MIDI_OFFSET+9)%12;	//C to A is 9
	}
	
	public static int filterdNote(int note,boolean isUpper,int[] scaleFilters){
		if(scaleFilters==null){
			return note;
		}
		Arrays.sort(scaleFilters);
		int myScale=noteToScale(note);
		log.info("note="+note+",scale="+myScale);
		for (int i = 0; i < scaleFilters.length; i++) {
			if(scaleFilters[i]==myScale){
				//no change;
				return note;
			}
		}
		int nearDown=256;
		int nearUp=256;
		for(int i=0;i<note_pitchs.length;i++){
			int tmpS=noteToScale(i+MIDI_OFFSET);
			SCALE:for(int j=0;j<scaleFilters.length;j++){
				if(scaleFilters[j]==tmpS && i+MIDI_OFFSET!=note){//support
					if(i+MIDI_OFFSET<note){
						nearDown=Math.min(nearDown, note-(i+MIDI_OFFSET));
					}else{
						nearUp=Math.min(nearUp, (i+MIDI_OFFSET)-note);
					}
					break SCALE;
				}
			}
		}
		
		if(nearDown==nearUp){
			if(nearDown==256){
				return note;//no match wrong case
			}
			if(isUpper){
				return note+nearUp;
			}else{
				return note-nearDown;
			}
		}else if(nearUp>nearDown){//small is better
			return note-nearDown;
		}else{
			return note+nearUp;
		}
		
		
		//return note;//finally
	}
	/*
	public static void main(String argv[]){
		
		for(int i=0;i<128;i++){
			//log.info(i+"="+noteNumberToPitch(i));
			//log.info(noteToPitch(i)+",");
		}
		
		
	}
	*/
	
	static double per=(double)10000/128;
	public static int toVelocity(double value){
		if(value<=0){
			return 0;
		}
		int v=(int)(value/per);
		
		return Math.min(v, 127);
	}


	
	public static class NoteData{
		private int start;
		private int end;
		private int note;
		private int velocity;
		private List<PitchNoteData> pitchNoteDatas=new ArrayList<PitchNoteData>();
		private List<Integer> velocityBent=new ArrayList<Integer>();
		private String comment;//for subtitle
		private PitchNoteData pitchNoteData;

		public String toString(){
			return "note="+note+",start="+start+",end="+end+",velocity="+velocity ;
							}
		public NoteData(){
			
		}
		public NoteData(int start,int end,int note,int velocity,PitchNoteData noteData){
			this(start,end,note,velocity);
			pitchNoteData=noteData;
		}
		public NoteData(int start,int end,int note,int velocity){
			this.start=start;
			this.end=end;
			this.note=note;
			this.velocity=velocity;
		}
		public int getEnd() {
			return end;
		}
		public void setEnd(int end) {
			this.end = end;
		}
		public int getNote() {
			return note;
		}
		public void setNote(int note) {
			this.note = note;
		}
		public int getStart() {
			return start;
		}
		public void setStart(int start) {
			this.start = start;
		}
		public int getVelocity() {
			return velocity;
		}
		public void setVelocity(int velocity) {
			this.velocity = velocity;
		}
		public List<PitchNoteData> getPitchNoteDatas() {
			return pitchNoteDatas;
		}
		public List<Integer> getVelocityBent() {
			return velocityBent;
		}
		public String getComment() {
			return comment;
		}
		public void setComment(String comment) {
			this.comment = comment;
		}
		public PitchNoteData getPitchNoteData() {
			return pitchNoteData;
		}
		public void setPitchNoteData(PitchNoteData pitchNoteData) {
			this.pitchNoteData = pitchNoteData;
		}
		
		
	}
	
	private List<NoteData> noVoices;
	private int lastNote;
	protected int pertick=128;	//tempo must be mod 16,how many tick 1/4 has
	private int tempo=240;//	//how many long millisecond 1/4 has,anyway 240 is 1sec
	protected int defaultNote=48;//for novoice final use	//C3

	public void makeMIDI(ESPS[] espses,File file) throws IOException{
log.info("makeMIDI:"+espses.length+" espses,file="+file.getAbsolutePath());
		
		Sequence seq=null;
		try {
			seq = new Sequence(Sequence.PPQ, pertick);
		} catch (InvalidMidiDataException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}//i don know if tempo is right
	    Track track=initializeTrack(seq,tempo,instrument);
	           
	            NoteData notes[]=espsesToNote(espses);
	        	for (int i = 0; i < notes.length; i++) {
	        		 System.out.println("note:"+notes[i].getNote()+",pitch="+espses[i].getPitch());//must
        		}
	            
	           
	            		//this time simple add
	            		for (int i = 0; i < notes.length; i++) {
	            			addNote(track, notes[i]);
	            		}
	            
	            if(track.size()==0){
	            	throw new IOException("empty midi");
	            }
	            
	            
	            MidiSystem.write(seq, 0,file);
	           
	}
	

	/*
	 * create midi with subtitle data
	 * start and end time is come from a subtitle
	 * but if there no voice don't join it.
	 */
	protected int getEspsesTick(){
		//pertick/16;//64 note  //int tick=30;//32 note , tempo 240/16 
		return pertick/16;
	}
	protected NoteData[] espsesToNote(ESPS[] espses){

		 List<NoteData> noteList=new ArrayList<NoteData>();
		 
		
	        
	        
	          //  int tick=pertick/16;//64 note  //int tick=30;//32 note , tempo 240/16 
	            int tick=getEspsesTick();
	            int current=0;
	            
	            if(!ignoreOffset){
	            	current=(int)(pertick*4*0.0075);//off set of 0.0075 * 960=7.2 ,*480 is maybe wrong
	            }
	            
	            log.info("offset:"+current);
	           for (int i = 0; i < espses.length; i++) {
	        	   PitchNoteData pd=null;
	        	   //has voice sound
	            	if(espses[i].getPitch()>0){
	            	log.info((pitchToNoteNumber(espses[i].getPitch(),scaleFilter))+" "+espses[i].getMeasurements());
	            	int note;
	            	
	            	if(parseNote){
	            		pd=pitchToNoteNumber(espses[i].getPitch(),scaleFilter);
	            		note=pd.getNote();
	            	}else{
	            		note=defaultNote;
	            	}
	            	
	            	int velocity;
	            	if(parseVelocity){
	            		velocity=toVelocity(espses[i].getMeasurements());
	            	}else{
	            		velocity=defaultVelocity;
	            	}
	            	
	            	//if no voice which has audio had started,add before them
	            	if(noVoices!=null){
	            		NoteData[] datas=noVoices.toArray(new NoteData[noVoices.size()]);
	            		for (int j = 0; j < datas.length; j++) {
	            			log.trace("start novoice");
	            			//note is same as this voiced note.
	            			addNote(noteList,datas[j].getStart(),datas[j].getEnd(),note,datas[j].getVelocity());
						}
	            		noVoices=null;
	            	}
	            	
	            	log.info("addNote:"+noteToMillisecond(current)+","+noteToMillisecond(current+tick)+","+(note)+","+velocity);
	            	addNote(noteList,current,current+tick,note,velocity,pd);//set pitch note data
		            lastNote=note;
		            //defaultNote=note;	//last voiced sound
	            	}else{//novoice or no sound.
	            		long startms=noteToMillisecond(current);
	            		long endms=noteToMillisecond(current+tick);
	            		log.trace("no pitch:"+startms+","+endms);
	            		/*
	            		 * 
	            		 if(startms>1850 && endms<1940){
	            			log.infomeasure:"+espses[i].getMeasurements()+","+noteToMillisecond(current)+","+noteToMillisecond(current+tick));
	            		}*/
	            		if(espses[i].getMeasurements()>volumeMin){
	            			log.trace("*0:"+espses[i].getMeasurements());
	            			int velocity=defaultVelocity;
	    	            	if(parseVelocity){
	    	            		velocity=toVelocity(espses[i].getMeasurements());
	    	            	}
	            			if(lastNote==0){//guess start;
	            				if(noVoices==null){
	            					noVoices=new ArrayList<NoteData>();
	            				}
	            				
	            				NoteData data=new NoteData(current,current+tick,0,velocity);
	            				
	            				noVoices.add(data);
	            			}else{
	            				//follow voiceded sound
	            				log.trace("end novoice");
	            				addNote(noteList,current,current+tick,lastNote,velocity);
	            			}
	            		}else{
	            		//independents no voice
	            		log.trace("0:"+espses[i].getMeasurements());
	            		if(noVoices!=null){
	            			log.info("no voice sounds");
		            		NoteData[] datas=noVoices.toArray(new NoteData[noVoices.size()]);
		            		for (int j = 0; j < datas.length; j++) {
		            			log.trace("start novoice");
		            			log.info("addNoVoice:"+datas[j].getVelocity()+","+noteToMillisecond(datas[j].getStart())+","+noteToMillisecond(datas[j].getEnd()));
		            			addNote(noteList,datas[j].getStart(),datas[j].getEnd(),defaultNote,datas[j].getVelocity());
							}
		            		noVoices=null;
		            	}
	            		noVoices=null;//reset
	            		lastNote=0;
	            		}
	            	}
	            	current+=tick;
				}
	           
	           //if remaining no voice 
	           if(noVoices!=null){//for remaining.
      			log.info("no voice sounds");
          		NoteData[] datas=noVoices.toArray(new NoteData[noVoices.size()]);
          		for (int j = 0; j < datas.length; j++) {
          			log.trace("start novoice");
          			log.info("addNoVoice:"+datas[j].getVelocity()+","+noteToMillisecond(datas[j].getStart())+","+noteToMillisecond(datas[j].getEnd()));
          			addNote(noteList,datas[j].getStart(),datas[j].getEnd(),defaultNote,datas[j].getVelocity());
					}
          		noVoices=null;
          		}
	     log.info(current+" tick, "+toSec((double)current/(pertick*4))+" sec");
		 return (NoteData[])noteList.toArray(new NoteData[noteList.size()]);
	}
	
	public static Track initializeTrack(Sequence seq,int tempo,int instrument){
		Track track=null;
		try{
		
		track  = seq.createTrack();
		 
		 //set tempo
		 MetaMessage tempoMessage = new MetaMessage();
		 int l = 60*1000000/tempo;
		 tempoMessage.setMessage(0x51, 
	                          new byte[]{(byte)(l/65536), (byte)(l%65536/256), (byte)(l%256)}, 
	                          3);  
       track.add(new MidiEvent(tempoMessage, 0));
      
       //set instrument
       ShortMessage message = new ShortMessage();
       message.setMessage(ShortMessage.PROGRAM_CHANGE, instrument, 0);
       track.add(new MidiEvent(message, 0));
		} catch (InvalidMidiDataException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
       return track;
	}
	protected List<NoteData> toNoteDataList(ESPS[] espses,StandardSubObject[] subtitles){
		 NoteData notes[]=espsesToNote(espses);
         List<NoteData> subNotes[]=new ArrayList[subtitles.length];
         if(subtitles.length>0){
         		for (int i = 0; i < notes.length; i++) {
         			CHECK:for (int j = 0; j < subtitles.length; j++) {
         				//log.infosubtitle:"+subtitles[j].getStartTime()+","+subtitles[j].getEndTime());
							if(inTime(notes[i],subtitles[j])){
								log.info("intime:"+noteToMillisecond(notes[i].getStart())+"-"+noteToMillisecond(notes[i].getEnd()));
								if(subNotes[j]==null){
									subNotes[j]=new ArrayList<NoteData>();
								}
								subNotes[j].add(notes[i]);
								break CHECK;
							}
						}
         		}
         }
         List<NoteData> noteDataList=new ArrayList<NoteData>();
         for(int i=0;i<subNotes.length;i++){
         	if(subNotes[i]!=null){
         		addAverageNote(noteDataList,subNotes[i],subtitles[i]);
         	}else{
         		if(forceInsert){
         			int minTick=pertick/16;//64 note  //int tick=30;//32 note , tempo 240/16 
         			int startTick=timeToTick(subtitles[i].getStartTime());
         			int endTick=timeToTick(subtitles[i].getEndTime());
         			int stick=startTick+(endTick-startTick-minTick)/2;
         			log.info("add empty note:s="+stick);
         			NoteData data=new NoteData(stick, stick+minTick, filterdNote(defaultNote, true, scaleFilter), defaultVelocity);
         			data.setComment(subtitles[i].getText());
         			noteDataList.add(data);
         		}
         	}
         }
         //debug
         for(int i=0;i<noteDataList.size();i++){
        	 //System.out.println("note:"+noteDataList.get(i).getNote());
         }
         return noteDataList;
	}
	public void makeMIDI(ESPS[] espses,StandardSubObject[] subtitles,File file) throws IOException{
		log.info("makeMIDI:"+espses.length+" espses,file="+file.getAbsolutePath());
		
		
	           
		List<NoteData> noteDataList=toNoteDataList(espses,subtitles);
	            
	           if(noteDataList.size()>0){
	            Sequence seq=null;
	    		try {
	    			seq = new Sequence(Sequence.PPQ, pertick);
	    		} catch (InvalidMidiDataException e) {
	    			// TODO Auto-generated catch block
	    			e.printStackTrace();
	    		}//i don know if tempo is right
	    	   
	            if(chorusLine!=null){
	            	int choruses[]=PitchToMIDI.parseChorus(chorusLine);
		            addChoruses(seq,choruses,noteDataList,tempo,instrument,shiftNote);
	    		}else{
	    			 Track track=initializeTrack(seq,tempo,instrument);
	    			 //add one note
		            for (int i = 0; i < noteDataList.size(); i++) {
						addNote(track,noteDataList.get(i));
					}
		            
	    		}
	    		
	            
	          
	            
	            
	            MidiSystem.write(seq, 1,file);	//0 make a error
	           }else{
	        	   throw new IOException("empty midi");//TODO
	           }
	            
	            
		
		
	}
	public static void addChoruses(Sequence seq, int choruses[],List<NoteData> noteDataList,int tempo,int instrument,int shiftNote) {
		
		Track tracks[]=new Track[choruses.length];
		for (int i = 0; i < choruses.length; i++) {
			tracks[i]=initializeTrack(seq,tempo,instrument);
			//tracks[i]=seq.createTrack();
		}
			for (int i = 0; i < noteDataList.size(); i++) {
				NoteData noteData=noteDataList.get(i);
				
				int notes[]=Chorus.getChorusNote(noteData.getNote(), choruses);
				for (int j = 0; j < notes.length; j++) {
					addNote(tracks[j],noteData.getStart(),noteData.getEnd(),notes[j],noteData.getVelocity(),shiftNote);
				}
				
			}
		
	}

	public double toSec(double v){
		return  new BigDecimal(v).setScale(2, BigDecimal.ROUND_HALF_UP)
        .doubleValue();
	}
	protected void addAverageNote(List<NoteData> container, List<NoteData> list,StandardSubObject subtitle) {
		NoteData notes[]=(NoteData[])list.toArray(new NoteData[list.size()]);
		int start=0;
		int end=0;
		long totalNote=0;
		long totalVelocity=0;
		NoteData data=new NoteData();
		for (int i = 0; i < notes.length; i++) {
			if(i==0){
				start=notes[i].start;
				end=notes[i].end;
			}else{
				start=Math.min(notes[i].getStart(), start);
				end=Math.max(notes[i].getEnd(), end);
			}
			totalNote+=notes[i].getNote();
			totalVelocity+=notes[i].getVelocity();
			//for bent data.
			if(notes[i].getPitchNoteData()!=null){
				data.getPitchNoteDatas().add(notes[i].getPitchNoteData());
			}else{
				data.getPitchNoteDatas().add(new PitchNoteData(notes[i].getNote(),0,0));
			}
			
			data.getVelocityBent().add(notes[i].getVelocity());
		}
		int note=(int) (totalNote/notes.length);
		int velocity=(int)(totalVelocity/notes.length);
		//log.info("start="+noteToMillisecond(start)+" end="+noteToMillisecond(end)+" note="+note+" velociy="+velocity);
		
		int filterd=filterdNote(note, true, scaleFilter);
		//int scale=noteToScale(filterd);//TODO what is this?
		
		
		//debug
		/*
		if(scale==0||scale==4||scale==7){
			//System.out.println("OK:note="+note+",scale="+scale);
		}else{
			//System.out.println("wrong:note="+note+",scale="+scale);
		}
		*/
		data.setComment(subtitle.getText());
		data.setStart(start);
		data.setEnd(end);
		data.setNote(filterd);
		data.setVelocity(velocity);
		container.add(data);
	}

	private double cal_1000=(double)1000/(pertick*4);//todo if tempo changed
	public boolean inTime(NoteData note,StandardSubObject sub){
		long start=(long) (note.getStart()*cal_1000);
		long end=(long) (note.getEnd()*cal_1000);
		return sub.intersect(start, end);	//start or end in sub time
	}
	
	public int timeToTick(long time){
		int tick=(int) (time/cal_1000);
		return tick;
	}
	/*
	public static class Note{
		private int start;
		private int end;
		private int note;
		private int velocity;
		public Note( int start,int end,int note,int velocity){
			this.start=start;
			this.end=end;
			this.note=note;
			this.velocity=velocity;
		}
		public int getEnd() {
			return end;
		}
		public void setEnd(int end) {
			this.end = end;
		}
		public int getNote() {
			return note;
		}
		public void setNote(int note) {
			this.note = note;
		}
		public int getStart() {
			return start;
		}
		public void setStart(int start) {
			this.start = start;
		}
		public int getVelocity() {
			return velocity;
		}
		public void setVelocity(int velocity) {
			this.velocity = velocity;
		}
	}*/
	
	public  void addNote(List<NoteData> list,int start,int end,int note,int velocity,PitchNoteData pitchNoteData){
		//anyway refilter
		note=filterdNote(note, true, scaleFilter);
		
		list.add(new NoteData(start,end,note,velocity,pitchNoteData));
	}
	
	public  void addNote(List<NoteData> list,int start,int end,int note,int velocity){
		//anyway refilter
		note=filterdNote(note, true, scaleFilter);
		
		list.add(new NoteData(start,end,note,velocity));
	}
	
	public  long noteToMillisecond(int value){
		return (long)(cal_1000*value);
	}
	
public  void addNote(Track track,NoteData note){
		addNote(track,note.getStart(),note.getEnd(),note.getNote(),note.getVelocity(),shiftNote);
	}

public  void addNoteData(Track track,NoteData note){
	addNote(track,note.getStart(),note.getEnd(),note.getNote(),note.getVelocity(),shiftNote);
}

/**
 * Java note should offset 12
 * @param track
 * @param start
 * @param end
 * @param note
 * @param velocity
 */
public static final int JAVA_NOTE_OFFSET=12;
	public   static void addNote(Track track,int start,int end,int note,int velocity,int shiftNote){
		ShortMessage noteOn1 = new ShortMessage();
    	
    System.out.println("addnote:"+note+","+(note+JAVA_NOTE_OFFSET+shiftNote));
         try {
			noteOn1.setMessage(ShortMessage.NOTE_ON, note+JAVA_NOTE_OFFSET+shiftNote, velocity);
			   track.add(new MidiEvent(noteOn1, start));
		         ShortMessage noteOn2 = new ShortMessage();
		        noteOn2.setMessage(ShortMessage.NOTE_OFF,note+JAVA_NOTE_OFFSET+shiftNote, 0);
		        track.add(new MidiEvent(noteOn2, end));
		} catch (InvalidMidiDataException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
      
	}
	//TODO support contain minor # or b
	public static int[] parseChorus(String line){
		List<Integer> list=new ArrayList<Integer>();
		String numLine=""; 
		int option=1;
		
		for(int i=0;i<line.length();i++){
			if(line.charAt(i)=='+'){
				if(numLine.length()>0){
					list.add(new Integer(Integer.parseInt(numLine))*option);
					numLine="";
				}
				option=1;
			}else if(line.charAt(i)=='-'){
				if(numLine.length()>0){
					list.add(new Integer(Integer.parseInt(numLine))*option);
					numLine="";
				}
				option=-1;
			}else if(Character.isDigit(line.charAt(i))){
				numLine+=line.charAt(i);
			}else{
				log.warn("contain wrong char:"+line);
				break;
			}
		}
		if(numLine.length()>0){
			list.add(new Integer(Integer.parseInt(numLine))*option);
			numLine="";
		}
		
		return ArrayUtils.toPrimitive(list.toArray(new Integer[list.size()]));
	}

	public void makeMIDI(Double[] pitchs,File file) throws IOException{
	//makeMIDI(ArrayUtils.toPrimitive(pitchs),file);
		ESPS[] esps=new ESPS[pitchs.length];
		for (int i = 0; i < pitchs.length; i++) {
			esps[i].setPitch(pitchs[i]);
			}
		makeMIDI(esps,file);
	}
	
	
	public void makeMIDI(double[] pitchs,File file) throws IOException{
		ESPS[] esps=new ESPS[pitchs.length];
		for (int i = 0; i < pitchs.length; i++) {
			esps[i].setPitch(pitchs[i]);
			}
		makeMIDI(esps,file);
		}
		

	public static double noteNumberToPitch(int note){
		note-=MIDI_OFFSET;
		if(note>=0 && note<note_pitchs.length){
			return note_pitchs[note];
		}else{
			return 0;//
		}
	}
	/**
	 * origin math
	public static double noteToPitch(int note){
		double v=1.059463094;
		double a_hz=440;
		int a_root=60;
		if(note==60){
			return 440;
		}
		double nv=a_hz;
		if(note<a_root){
			int n=a_root-note;
			
			for(int i=0;i<n;i++){
				nv=nv/v;
			}
			
			
		}else{
			int n=note-a_root;
			
			for(int i=0;i<n;i++){
				nv=nv*v;
			}
			
		}
		return nv;
	}*/

	public int getShiftNote() {
		return shiftNote;
	}

	public void setShiftNote(int shiftNote) {
		this.shiftNote = shiftNote;
	}

	public int getVolumeMin() {
		return volumeMin;
	}

	public void setVolumeMin(int volumeMin) {
		this.volumeMin = volumeMin;
	}

	public int getTempo() {
		return tempo;
	}

	public void setTempo(int tempo) {
		this.tempo = tempo;
	}

	public boolean isIgnoreOffset() {
		return ignoreOffset;
	}

	/**
	 * ignore first offset of 0.0075 sec
	 * @param ignoreOffset
	 */
	public void setIgnoreOffset(boolean ignoreOffset) {
		this.ignoreOffset = ignoreOffset;
	}

	public boolean isForceInsert() {
		return forceInsert;
	}

	public void setForceInsert(boolean allowEmptySubtitleNote) {
		this.forceInsert = allowEmptySubtitleNote;
	}

	public int[] getScaleFilter() {
		return scaleFilter;
	}

	public void setScaleFilter(int[] scaleFilter) {
		this.scaleFilter = scaleFilter;
	}

	public int getDefaultVelocity() {
		return defaultVelocity;
	}

	public void setDefaultVelocity(int defaultVelocity) {
		this.defaultVelocity = defaultVelocity;
	}

	public boolean isParseVelocity() {
		return parseVelocity;
	}

	public void setParseVelocity(boolean parseVelocity) {
		this.parseVelocity = parseVelocity;
	}

	public boolean isParseNote() {
		return parseNote;
	}

	public void setParseNote(boolean parseNote) {
		this.parseNote = parseNote;
	}

	public int getDefaultNote() {
		return defaultNote;
	}

	public void setDefaultNote(int defaultNote) {
		this.defaultNote = defaultNote;
	}

	public String getChorusLine() {
		return chorusLine;
	}

	public void setChorusLine(String chorusLine) {
		this.chorusLine = chorusLine;
	}

	public int getPertick() {
		return pertick;
	}

	public void setPertick(int pertick) {
		this.pertick = pertick;
	}


	
	
}
